1   /*
2    * Copyright (C) 2007 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.base;
18  
19  import com.google.common.annotations.GwtCompatible;
20  import com.google.common.annotations.GwtIncompatible;
21  import com.google.common.testing.GcFinalization;
22  
23  import junit.framework.TestCase;
24  
25  import java.lang.ref.WeakReference;
26  import java.util.Iterator;
27  import java.util.NoSuchElementException;
28  
29  /**
30   * Unit test for {@code AbstractIterator}.
31   *
32   * @author Kevin Bourrillion
33   */
34  @GwtCompatible(emulated = true)
35  // TODO(cpovirk): why is this slow (>1m/test) under GWT when fully optimized?
36  public class AbstractIteratorTest extends TestCase {
37  
38    public void testDefaultBehaviorOfNextAndHasNext() {
39  
40      // This sample AbstractIterator returns 0 on the first call, 1 on the
41      // second, then signals that it's reached the end of the data
42      Iterator<Integer> iter = new AbstractIterator<Integer>() {
43        private int rep;
44        @Override public Integer computeNext() {
45          switch (rep++) {
46            case 0:
47              return 0;
48            case 1:
49              return 1;
50            case 2:
51              return endOfData();
52            default:
53              fail("Should not have been invoked again");
54              return null;
55          }
56        }
57      };
58  
59      assertTrue(iter.hasNext());
60      assertEquals(0, (int) iter.next());
61  
62      // verify idempotence of hasNext()
63      assertTrue(iter.hasNext());
64      assertTrue(iter.hasNext());
65      assertTrue(iter.hasNext());
66      assertEquals(1, (int) iter.next());
67  
68      assertFalse(iter.hasNext());
69  
70      // Make sure computeNext() doesn't get invoked again
71      assertFalse(iter.hasNext());
72  
73      try {
74        iter.next();
75        fail("no exception thrown");
76      } catch (NoSuchElementException expected) {
77      }
78    }
79  
80    public void testSneakyThrow() throws Exception {
81      Iterator<Integer> iter = new AbstractIterator<Integer>() {
82        boolean haveBeenCalled;
83        @Override public Integer computeNext() {
84          if (haveBeenCalled) {
85            fail("Should not have been called again");
86          } else {
87            haveBeenCalled = true;
88            sneakyThrow(new SomeCheckedException());
89          }
90          return null; // never reached
91        }
92      };
93  
94      // The first time, the sneakily-thrown exception comes out
95      try {
96        iter.hasNext();
97        fail("No exception thrown");
98      } catch (Exception e) {
99        if (!(e instanceof SomeCheckedException)) {
100         throw e;
101       }
102     }
103 
104     // But the second time, AbstractIterator itself throws an ISE
105     try {
106       iter.hasNext();
107       fail("No exception thrown");
108     } catch (IllegalStateException expected) {
109     }
110   }
111 
112   public void testException() {
113     final SomeUncheckedException exception = new SomeUncheckedException();
114     Iterator<Integer> iter = new AbstractIterator<Integer>() {
115       @Override public Integer computeNext() {
116         throw exception;
117       }
118     };
119 
120     // It should pass through untouched
121     try {
122       iter.hasNext();
123       fail("No exception thrown");
124     } catch (SomeUncheckedException e) {
125       assertSame(exception, e);
126     }
127   }
128 
129   public void testExceptionAfterEndOfData() {
130     Iterator<Integer> iter = new AbstractIterator<Integer>() {
131       @Override public Integer computeNext() {
132         endOfData();
133         throw new SomeUncheckedException();
134       }
135     };
136     try {
137       iter.hasNext();
138       fail("No exception thrown");
139     } catch (SomeUncheckedException expected) {
140     }
141   }
142 
143   public void testCantRemove() {
144     Iterator<Integer> iter = new AbstractIterator<Integer>() {
145       boolean haveBeenCalled;
146       @Override public Integer computeNext() {
147         if (haveBeenCalled) {
148           endOfData();
149         }
150         haveBeenCalled = true;
151         return 0;
152       }
153     };
154 
155     assertEquals(0, (int) iter.next());
156 
157     try {
158       iter.remove();
159       fail("No exception thrown");
160     } catch (UnsupportedOperationException expected) {
161     }
162   }
163 
164   @GwtIncompatible("weak references")
165   public void testFreesNextReference() {
166     Iterator<Object> itr = new AbstractIterator<Object>() {
167       @Override public Object computeNext() {
168         return new Object();
169       }
170     };
171     WeakReference<Object> ref = new WeakReference<Object>(itr.next());
172     GcFinalization.awaitClear(ref);
173   }
174 
175   public void testReentrantHasNext() {
176     Iterator<Integer> iter = new AbstractIterator<Integer>() {
177       @Override protected Integer computeNext() {
178         hasNext();
179         return null;
180       }
181     };
182     try {
183       iter.hasNext();
184       fail();
185     } catch (IllegalStateException expected) {
186     }
187   }
188 
189   // Technically we should test other reentrant scenarios (4 combinations of
190   // hasNext/next), but we'll cop out for now, knowing that
191   // next() both start by invoking hasNext() anyway.
192 
193   /**
194    * Throws a undeclared checked exception.
195    */
196   private static void sneakyThrow(Throwable t) {
197     class SneakyThrower<T extends Throwable> {
198       @SuppressWarnings("unchecked") // intentionally unsafe for test
199       void throwIt(Throwable t) throws T {
200         throw (T) t;
201       }
202     }
203     new SneakyThrower<Error>().throwIt(t);
204   }
205 
206   private static class SomeCheckedException extends Exception {
207   }
208 
209   private static class SomeUncheckedException extends RuntimeException {
210   }
211 }